package com.catal.framework.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.jdbc.datasource.DataSourceUtils;

import com.catal.framework.dao.IJdbcDao;
import com.catal.framework.query.DBDataColumnMap;

public class JdbcDaoImpl  extends JdbcDaoSupport implements IJdbcDao{
	protected static Log log = LogFactory.getLog(JdbcDaoImpl.class);

	private static ColumnMapRowMapper mapper = new DBDataColumnMap();

	private String[] pkColumns;

	private String sql = null;

	private String tableName;

	protected List columnNames = new ArrayList();

	protected Map fieldtypes = new HashMap();
	
	/**
	 * 操作对象(功能名称)
	 */
	protected String operObject;

	public JdbcDaoImpl() {
		
	}

	public JdbcDaoImpl(String tableName, String pkName, DataSource datasource) {
		this.tableName = tableName;
		setPkName(pkName);
		setDataSource(datasource);
	}

	protected void formatResult(Map result) {
	}

	protected void formatParameter(Map parameter) {
	}

	public void initColumnNames() {
		columnNames.clear();
		fieldtypes.clear();
		StringBuffer buffer = new StringBuffer();
		buffer.append("select * from ").append(this.tableName)
				.append(" where 1=2 ");

		Connection connection = null;
		PreparedStatement statement = null;
		ResultSet result = null;
		try {
			DataSource dataSource = getDataSource();
			connection = DataSourceUtils.getConnection(dataSource);
			statement = connection.prepareStatement(buffer.toString());
			result = statement.executeQuery();

			ResultSetMetaData metaData = result.getMetaData();
			for (int i = 1; i <= metaData.getColumnCount(); i++) {
				String columnName = metaData.getColumnName(i).toLowerCase();

				columnNames.add(columnName);
				fieldtypes.put(columnName,
						new Integer(metaData.getColumnType(i)));

			}
		} catch (SQLException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		} finally {
			try {
				result.close();
				statement.close();
				DataSourceUtils.releaseConnection(connection, getDataSource());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		String whereClause = " where " + pkColumns[0] + "=? ";
		for (int i = 1; i < pkColumns.length; i++) {
			whereClause += " and " + pkColumns[i] + "=? ";
		}

		sql = "select * from " + tableName + whereClause;
	}

	public Map<String, Object> defaultValues() {
		return new HashMap<String, Object>();
	}

	private static StringBuffer arrayToString(Object[] objs) {
		StringBuffer buffer = new StringBuffer();

		for (int i = 0; i < objs.length; i++) {
			buffer.append(objs[i] == null ? "null" : objs[i].toString());
			buffer.append(",");
		}

		return buffer;
	}

	private Object[] getPkValues(Map values) {
		Object[] pkValues = new Object[pkColumns.length];

		for (int i = 0; i < pkValues.length; i++) {
			pkValues[i] = values.get(pkColumns[i]);

			if (pkValues[i] == null) {
				throw new RuntimeException("找不到主键[" + pkColumns[i] + "]的值!");
			}
		}

		return pkValues;
	}

	private Map internalLoadByID(Object[] pkValues) {
		initColumnNames();
		log.info("internalLoadFromID: " + sql + "[" + arrayToString(pkValues)
				+ "]");

		Map result = (Map) getJdbcTemplate().queryForObject(sql.toString(),
				pkValues, mapper);
		formatResult(result);

		return result;
	}

	private String getUpdateSql(String tableName, String[] pkColumns,
			List columnNames, Object[] pkValues, Map values, List params) {
		params.clear();

		StringBuffer sql = new StringBuffer();
		sql.append("update ").append(tableName).append(" set ");

		List pairs = new ArrayList();
		for (int i = 0; i < columnNames.size(); i++) {
			String key = (String) columnNames.get(i);
			Object value = values.get(key);
			if (value != null) {
				pairs.add(new Object[] { key, value });
			}
		}

		if (pairs.size() == 0) {
			throw new RuntimeException("error");
		}

		for (int i = 0; i < pairs.size(); i++) {
			Object[] pair = (Object[]) pairs.get(i);
			sql.append(pair[0]).append(" = ? ");

			if (i != pairs.size() - 1) {
				sql.append(", ");
			}

			params.add(pair[1]);
		}

		String whereClause = " where " + pkColumns[0] + "=? ";
		for (int i = 1; i < pkColumns.length; i++) {
			whereClause += " and " + pkColumns[i] + "=? ";
		}
		sql.append(whereClause);

		if (pkValues.length != pkColumns.length) {
			throw new RuntimeException("primary key error:" + pkValues);
		}

		for (int i = 0; i < pkValues.length; i++) {
			params.add(pkValues[i]);
		}

		return sql.toString();
	}

	private String getInsertSql(String tableName, String[] pkColumns,
			List columnNames, Map values, List params) {
		params.clear();

		StringBuffer sql = new StringBuffer();
		sql.append("insert into ").append(tableName).append("(");

		List pairs = new ArrayList();
		for (int i = 0; i < columnNames.size(); i++) {
			String key = (String) columnNames.get(i);
			Object value = values.get(key);
			if (value != null) {
				pairs.add(new Object[] { key, value });
			}
		}

		if (pairs.size() == 0) {
			throw new RuntimeException("error");
		}

		for (int i = 0; i < pairs.size(); i++) {
			Object[] pair = (Object[]) pairs.get(i);
			sql.append(pair[0]);
			if (i != pairs.size() - 1) {
				sql.append(", ");
			}
		}
		sql.append(")values(");

		for (int i = 0; i < pairs.size(); i++) {
			Object[] pair = (Object[]) pairs.get(i);
			sql.append("?");

			if (i != pairs.size() - 1) {
				sql.append(", ");
			}

			params.add(pair[1]);
		}

		sql.append(")");

		return sql.toString();
	}

	private String getDeleteSql(String tableName, String[] pkColumns,
			Object[] pkValues, List params) {
		StringBuffer sql = new StringBuffer();
		sql.append("delete  from ").append(tableName);

		String whereClause = " where " + pkColumns[0] + "=? ";
		for (int i = 1; i < pkColumns.length; i++) {
			whereClause += " and " + pkColumns[i] + "=? ";
		}
		sql.append(whereClause);

		if (pkValues.length != pkColumns.length) {
			throw new RuntimeException("primary key error:" + pkValues);
		}

		for (int i = 0; i < pkValues.length; i++) {
			params.add(pkValues[i]);
		}

		return sql.toString();
	}

	private void internalDelete(Object[] pkValues) {
		List params = new ArrayList();
		String sql = getDeleteSql(tableName, pkColumns, pkValues, params);

		log.info("deleteRecord:" + sql + ":[" + params.toString() + "]");

		getJdbcTemplate().update(sql.toString(), params.toArray());
	}

	public Map loadByID(String id) {
		Object[] pkValues = id.split(",", -2);

		Map keyMap = new HashMap();
		if (pkColumns.length != pkValues.length) {
			throw new RuntimeException("pkColumns的长度和pkValues的长度不一致!");
		}

		for (int i = 0; i < pkValues.length; i++) {
			keyMap.put(pkColumns[i], pkValues[i]);
		}

		formatParameter(keyMap);

		return internalLoadByID(pkValues);
	}

	public Map loadByValues(Map values) {
		formatParameter(values);

		Object[] pkValues = getPkValues(values);

		return internalLoadByID(pkValues);
	}

	public Map insertRecord(Map values) {
		if(columnNames.size() == 0){
			initColumnNames();
		}
		formatParameter(values);
		Object[] pkValues = getPkValues(values);

		List params = new ArrayList();
		String sql = getInsertSql(tableName, pkColumns, columnNames, values,
				params);

		log.info("insertRecord:" + sql + ":[" + params.toString() + "]");

		try {
			getJdbcTemplate().update(sql.toString(), params.toArray());
		} catch (DataIntegrityViolationException e) {
			throw new RuntimeException("插入数据时违反数据库完整性约束，请检查!");
		}

		List result = getJdbcTemplate().query(this.sql, pkValues, mapper);
		if (result.size() != 1) {
			throw new RuntimeException("update" + " 错误！！");
		}

		Map m = (Map) result.get(0);
		formatResult(m);

		return m;
	}

	public Map updateRecord(Map values) {
		if(columnNames.size() == 0){
			initColumnNames();
		}
		
		formatParameter(values);

		Object[] pkValues = getPkValues(values);
		List params = new ArrayList();

		String sql = getUpdateSql(tableName, pkColumns, columnNames, pkValues,
				values, params);
		log.info("updateRecord:" + sql + ":[" + params.toString() + "]");

		getJdbcTemplate().update(sql.toString(), params.toArray());

		List result = getJdbcTemplate().query(this.sql, pkValues, mapper);
		if (result.size() != 1) {
			throw new RuntimeException("update" + " 错误！！没有找到相应的数据进行更新:["
					+ params.toString() + "]");
		}

		Map m = (Map) result.get(0);
		formatResult(m);

		return m;
	}

	public Map saveOrUpdate(Map values) {
		Object[] pkValues = getPkValues(values);

		List result = getJdbcTemplate().queryForList(sql, pkValues);
		if (result.size() == 0) {
			return insertRecord(values);
		} else {
			return updateRecord(values);
		}
	}

	public void deleteByID(String id) {
		String[] pkValues = id.split(",", -2);
		internalDelete(pkValues);
	}

	public void deleteByValues(Map values) {
		Object[] pkValues = getPkValues(values);
		internalDelete(pkValues);
	}

	public String getPkName() {
		String result = pkColumns[0];

		for (int i = 1; i < pkColumns.length; i++) {
			result += "," + pkColumns[i];
		}

		return result;
	}

	public void setPkName(String pkName) {
		pkColumns = pkName.split(",");
	}

	public String getTableName() {
		return tableName;
	}

	public void setTableName(String tableName) {
		this.tableName = tableName;
	}

	public String[] getPkColumns() {
		return pkColumns;
	}

	public String getOperObject() {
		return operObject;
	}

	public void setOperObject(String operObject) {
		this.operObject = operObject;
	}
}
